/*-------------------------------------------------------------------------
 *
 * tsquery_rewrite.c
 *	  用于重构 tsquery 的实用程序
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/tsquery_rewrite.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "catalog/pg_type.h"
#include "executor/spi.h"
#include "miscadmin.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"


/*
 * 如果“node”等于“ex”，则返回“subs”的副本。
 * 如果“ex”与node的某个子节点的子集匹配，则返回一个修改后的version
 * node，其中这些子节点替换为subs的副本。
 * 否则返回未修改的“node”。
 *
 * 在成功修改的节点中设置QTN_NOCHANGE位，以便我们不会无谓地递归进入它们。
 * 如果我们进行了替换，还要将*isfind设置为true。
 */
static QTNode * fc_findeq(QTNode *fc_node, QTNode *fc_ex, QTNode *fc_subs, bool *fc_isfind)
{
	/* 只有在签名匹配和节点类型匹配的情况下才能匹配。 */
	if ((fc_node->sign & fc_ex->sign) != fc_ex->sign ||
		fc_node->valnode->type != fc_ex->valnode->type)
		return fc_node;

	/* 也忽略标记为NOCHANGE的节点。 */
	if (fc_node->flags & QTN_NOCHANGE)
		return fc_node;

	if (fc_node->valnode->type == QI_OPR)
	{
		/* 必须是同一操作符。 */
		if (fc_node->valnode->qoperator.oper != fc_ex->valnode->qoperator.oper)
			return fc_node;

		if (fc_node->nchild == fc_ex->nchild)
		{
			/*
			 * 简单情况：当拥有相同数量的子节点时，如果相等则匹配。
			 * （当子节点之前已排序时，这是可靠的。）
			 */
			if (QTNEq(fc_node, fc_ex))
			{
				/* 匹配；删除节点并返回subs的副本。 */
				QTNFree(fc_node);
				if (fc_subs)
				{
					fc_node = QTNCopy(fc_subs);
					fc_node->flags |= QTN_NOCHANGE;
				}
				else
					fc_node = NULL;
				*fc_isfind = true;
			}
		}
		else if (fc_node->nchild > fc_ex->nchild && fc_ex->nchild > 0)
		{
			/*
			 * AND和OR是可交换/结合的，所以我们应该检查是否有一个
			 * 子集的子节点匹配。例如，如果节点是A|B|C，
			 * 而ex是B|C，经过名义转换后，我们就得到了匹配node
			 * 为A|(B|C)。这在NOT或PHRASE节点不适用，但我们
			 * 无法到达这些节点类型，因为它们具有固定的
			 * 子节点数量。
			 *
			 * 因为我们期望子节点是排序的，因此只需通过两个列表
			 * 进行一次遍历以找到匹配项。
			 */
			bool	   *fc_matched;
			int			fc_nmatched;
			int			fc_i,
						fc_j;

			/* 断言子集规则是OK */
			Assert(fc_node->valnode->qoperator.oper == OP_AND ||
				   fc_node->valnode->qoperator.oper == OP_OR);

			/* matched[]将记录node的哪些子节点匹配 */
			fc_matched = (bool *) palloc0(fc_node->nchild * sizeof(bool));
			fc_nmatched = 0;
			fc_i = fc_j = 0;
			while (fc_i < fc_node->nchild && fc_j < fc_ex->nchild)
			{
				int			fc_cmp = QTNodeCompare(fc_node->child[fc_i], fc_ex->child[fc_j]);

				if (fc_cmp == 0)
				{
					/* match! */
					fc_matched[fc_i] = true;
					fc_nmatched++;
					fc_i++, fc_j++;
				}
				else if (fc_cmp < 0)
				{
					/* node->child[i]没有匹配，忽略它 */
					fc_i++;
				}
				else
				{
					/* ex->child[j]没有匹配；我们可以立即放弃 */
					break;
				}
			}

			if (fc_nmatched == fc_ex->nchild)
			{
				/* 合并node的匹配子节点 */
				fc_j = 0;
				for (fc_i = 0; fc_i < fc_node->nchild; fc_i++)
				{
					if (fc_matched[fc_i])
						QTNFree(fc_node->child[fc_i]);
					else
						fc_node->child[fc_j++] = fc_node->child[fc_i];
				}

				/* 并插入subs的副本 */
				if (fc_subs)
				{
					fc_subs = QTNCopy(fc_subs);
					fc_subs->flags |= QTN_NOCHANGE;
					fc_node->child[fc_j++] = fc_subs;
				}

				fc_node->nchild = fc_j;

				/*
				 * 此时我们可能有一个具有零或一个子节点的节点，
				 * 应该简化。但是我们将其留给我们的调用者
				 * (dofindsubquery)来处理。
				 */

				/*
				 * 重新排序节点以将新子节点放置在正确的位置。
				 * 这有点虚假，因为对于findsubquery的
				 * 剩余处理这并不重要，并且不足以为另一次搜索准备
				 * 树（我们还需要重新扁平化，并且我们不想那么做
				 * 因为我们会丢失新子节点上的QTN_NOCHANGE标记）。但是，它是必须的，以
				 * 保持结果与回归测试期待的相同。
				 */
				QTNSort(fc_node);

				*fc_isfind = true;
			}

			pfree(fc_matched);
		}
	}
	else
	{
		Assert(fc_node->valnode->type == QI_VAL);

		if (fc_node->valnode->qoperand.valcrc != fc_ex->valnode->qoperand.valcrc)
			return fc_node;
		else if (QTNEq(fc_node, fc_ex))
		{
			QTNFree(fc_node);
			if (fc_subs)
			{
				fc_node = QTNCopy(fc_subs);
				fc_node->flags |= QTN_NOCHANGE;
			}
			else
			{
				fc_node = NULL;
			}
			*fc_isfind = true;
		}
	}

	return fc_node;
}

/*
 * findsubquery()的递归核心：尝试以“subs”替换“ex”
 * 在根节点处，如果我们未能做到这一点，则递归地
 * 与子节点匹配。
 *
 * 删除任何因替换而导致的空子树。
 * 在下面的示例中，'5'被空操作数替换：
 *
 *	  AND		->	  6
 *	 /	 \
 *	5	 OR
 *		/  \
 *	   6	5
 */
static QTNode * fc_dofindsubquery(QTNode *fc_root, QTNode *fc_ex, QTNode *fc_subs, bool *fc_isfind)
{
	/* 由于这个函数递归，它可能导致栈溢出。 */
	check_stack_depth();

	/* 此外，考虑到花费有点高，让我们检查查询取消。 */
	CHECK_FOR_INTERRUPTS();

	/* 在节点本身匹配 */
	fc_root = fc_findeq(fc_root, fc_ex, fc_subs, fc_isfind);

	/* 除非我们在这里匹配，否则考虑匹配子节点 */
	if (fc_root && (fc_root->flags & QTN_NOCHANGE) == 0 &&
		fc_root->valnode->type == QI_OPR)
	{
		int			fc_i,
					fc_j = 0;

		/*
		 * 任何被NULL替换的子树必须从树中删除。
		 */
		for (fc_i = 0; fc_i < fc_root->nchild; fc_i++)
		{
			fc_root->child[fc_j] = fc_dofindsubquery(fc_root->child[fc_i], fc_ex, fc_subs, fc_isfind);
			if (fc_root->child[fc_j])
				fc_j++;
		}

		fc_root->nchild = fc_j;

		/*
		 * 如果我们只剩下零个或一个子节点，则简化此
		 * 操作符节点。
		 */
		if (fc_root->nchild == 0)
		{
			QTNFree(fc_root);
			fc_root = NULL;
		}
		else if (fc_root->nchild == 1 && fc_root->valnode->qoperator.oper != OP_NOT)
		{
			QTNode	   *fc_nroot = fc_root->child[0];

			pfree(fc_root);
			fc_root = fc_nroot;
		}
	}

	return fc_root;
}

/*
 * 在根节点的QTNode树中替换“ex”为“subs”。
 *
 * 如果isfind不是NULL，则将*isfind设置为显示我们是否进行了任何替换。
 *
 * “root”和“ex”必须经过QTNTernary和QTNSort
 * 以确保可靠匹配。
 */
QTNode * findsubquery(QTNode *fc_root, QTNode *fc_ex, QTNode *fc_subs, bool *fc_isfind)
{
	bool		fc_DidFind = false;

	fc_root = fc_dofindsubquery(fc_root, fc_ex, fc_subs, &fc_DidFind);

	if (fc_isfind)
		*fc_isfind = fc_DidFind;

	return fc_root;
}

Datum tsquery_rewrite_query(PG_FUNCTION_ARGS)
{
	TSQuery		fc_query = PG_GETARG_TSQUERY_COPY(0);
	text	   *fc_in = PG_GETARG_TEXT_PP(1);
	TSQuery		fc_rewrited = fc_query;
	MemoryContext fc_outercontext = CurrentMemoryContext;
	MemoryContext fc_oldcontext;
	QTNode	   *fc_tree;
	char	   *fc_buf;
	SPIPlanPtr	fc_plan;
	Portal		fc_portal;
	bool		fc_isnull;

	if (fc_query->size == 0)
	{
		PG_FREE_IF_COPY(fc_in, 1);
		PG_RETURN_POINTER(fc_rewrited);
	}

	fc_tree = QT2QTN(GETQUERY(fc_query), GETOPERAND(fc_query));
	QTNTernary(fc_tree);
	QTNSort(fc_tree);

	fc_buf = text_to_cstring(fc_in);

	SPI_connect();

	if ((fc_plan = SPI_prepare(fc_buf, 0, NULL)) == NULL)
		elog(ERROR, "SPI_prepare(\"%s\") failed", fc_buf);

	if ((fc_portal = SPI_cursor_open(NULL, fc_plan, NULL, NULL, true)) == NULL)
		elog(ERROR, "SPI_cursor_open(\"%s\") failed", fc_buf);

	SPI_cursor_fetch(fc_portal, true, 100);

	if (SPI_tuptable == NULL ||
		SPI_tuptable->tupdesc->natts != 2 ||
		SPI_gettypeid(SPI_tuptable->tupdesc, 1) != TSQUERYOID ||
		SPI_gettypeid(SPI_tuptable->tupdesc, 2) != TSQUERYOID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("ts_rewrite query must return two tsquery columns")));

	while (SPI_processed > 0 && fc_tree)
	{
		uint64		fc_i;

		for (fc_i = 0; fc_i < SPI_processed && fc_tree; fc_i++)
		{
			Datum		fc_qdata = SPI_getbinval(SPI_tuptable->vals[fc_i], SPI_tuptable->tupdesc, 1, &fc_isnull);
			Datum		fc_sdata;

			if (fc_isnull)
				continue;

			fc_sdata = SPI_getbinval(SPI_tuptable->vals[fc_i], SPI_tuptable->tupdesc, 2, &fc_isnull);

			if (!fc_isnull)
			{
				TSQuery		fc_qtex = DatumGetTSQuery(fc_qdata);
				TSQuery		fc_qtsubs = DatumGetTSQuery(fc_sdata);
				QTNode	   *fc_qex,
						   *fc_qsubs = NULL;

				if (fc_qtex->size == 0)
				{
					if (fc_qtex != (TSQuery) DatumGetPointer(fc_qdata))
						pfree(fc_qtex);
					if (fc_qtsubs != (TSQuery) DatumGetPointer(fc_sdata))
						pfree(fc_qtsubs);
					continue;
				}

				fc_qex = QT2QTN(GETQUERY(fc_qtex), GETOPERAND(fc_qtex));

				QTNTernary(fc_qex);
				QTNSort(fc_qex);

				if (fc_qtsubs->size)
					fc_qsubs = QT2QTN(GETQUERY(fc_qtsubs), GETOPERAND(fc_qtsubs));

				fc_oldcontext = MemoryContextSwitchTo(fc_outercontext);
				fc_tree = findsubquery(fc_tree, fc_qex, fc_qsubs, NULL);
				MemoryContextSwitchTo(fc_oldcontext);

				QTNFree(fc_qex);
				if (fc_qtex != (TSQuery) DatumGetPointer(fc_qdata))
					pfree(fc_qtex);
				QTNFree(fc_qsubs);
				if (fc_qtsubs != (TSQuery) DatumGetPointer(fc_sdata))
					pfree(fc_qtsubs);

				if (fc_tree)
				{
					/* 为另一次遍历准备树 */
					QTNClearFlags(fc_tree, QTN_NOCHANGE);
					QTNTernary(fc_tree);
					QTNSort(fc_tree);
				}
			}
		}

		SPI_freetuptable(SPI_tuptable);
		SPI_cursor_fetch(fc_portal, true, 100);
	}

	SPI_freetuptable(SPI_tuptable);
	SPI_cursor_close(fc_portal);
	SPI_freeplan(fc_plan);
	SPI_finish();

	if (fc_tree)
	{
		QTNBinary(fc_tree);
		fc_rewrited = QTN2QT(fc_tree);
		QTNFree(fc_tree);
		PG_FREE_IF_COPY(fc_query, 0);
	}
	else
	{
		SET_VARSIZE(fc_rewrited, HDRSIZETQ);
		fc_rewrited->size = 0;
	}

	pfree(fc_buf);
	PG_FREE_IF_COPY(fc_in, 1);
	PG_RETURN_POINTER(fc_rewrited);
}

Datum tsquery_rewrite(PG_FUNCTION_ARGS)
{
	TSQuery		fc_query = PG_GETARG_TSQUERY_COPY(0);
	TSQuery		fc_ex = PG_GETARG_TSQUERY(1);
	TSQuery		fc_subst = PG_GETARG_TSQUERY(2);
	TSQuery		fc_rewrited = fc_query;
	QTNode	   *fc_tree,
			   *fc_qex,
			   *fc_subs = NULL;

	if (fc_query->size == 0 || fc_ex->size == 0)
	{
		PG_FREE_IF_COPY(fc_ex, 1);
		PG_FREE_IF_COPY(fc_subst, 2);
		PG_RETURN_POINTER(fc_rewrited);
	}

	fc_tree = QT2QTN(GETQUERY(fc_query), GETOPERAND(fc_query));
	QTNTernary(fc_tree);
	QTNSort(fc_tree);

	fc_qex = QT2QTN(GETQUERY(fc_ex), GETOPERAND(fc_ex));
	QTNTernary(fc_qex);
	QTNSort(fc_qex);

	if (fc_subst->size)
		fc_subs = QT2QTN(GETQUERY(fc_subst), GETOPERAND(fc_subst));

	fc_tree = findsubquery(fc_tree, fc_qex, fc_subs, NULL);

	QTNFree(fc_qex);
	QTNFree(fc_subs);

	if (!fc_tree)
	{
		SET_VARSIZE(fc_rewrited, HDRSIZETQ);
		fc_rewrited->size = 0;
		PG_FREE_IF_COPY(fc_ex, 1);
		PG_FREE_IF_COPY(fc_subst, 2);
		PG_RETURN_POINTER(fc_rewrited);
	}
	else
	{
		QTNBinary(fc_tree);
		fc_rewrited = QTN2QT(fc_tree);
		QTNFree(fc_tree);
	}

	PG_FREE_IF_COPY(fc_query, 0);
	PG_FREE_IF_COPY(fc_ex, 1);
	PG_FREE_IF_COPY(fc_subst, 2);
	PG_RETURN_POINTER(fc_rewrited);
}
